package com.liush.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liush.srb.base.result.ResponseEnum;
import com.liush.srb.base.util.SrbAssert;
import com.liush.srb.core.hfb.HfbConst;
import com.liush.srb.core.hfb.RequestHelper;
import com.liush.srb.core.pojo.entity.*;
import com.liush.srb.core.mapper.LendMapper;
import com.liush.srb.core.pojo.vo.*;
import com.liush.srb.core.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liush.srb.core.util.*;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author liush
 * @since 2023-03-25
 */
@Service
@Transactional(rollbackFor = Exception.class)//默认RuntimeException回滚
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    BorrowInfoService borrowInfoService;
    @Resource
    BorrowerService borrowerService;
    @Resource
    DictService dictService;
    @Resource
    LendItemService lendItemService;
    @Resource
    UserInfoService userInfoService;
    @Resource
    UserAccountService userAccountService;
    @Resource
    LendReturnService lendReturnService;
    @Resource
    LendItemReturnService lendItemReturnService;
    @Resource
    TransFlowService transFlowService;

    @Override
    public void createLend(BorrowInfoApprovalVO borrowInfoApprovalVO) {
        Lend lend = new Lend();
        BeanUtils.copyProperties(borrowInfoApprovalVO, lend);
        //手动设置标的状态为待发布状态： 需要管理员审批发布
        lend.setStatus(0);//状态(0:待发布,1:募资中,2:还款中,3:已结清,4:结标,-1:已撤标)
        Date date = null;
        try {
            date = new SimpleDateFormat().parse(borrowInfoApprovalVO.getLendStartDate());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        lend.setLendEndDate(date);
        //查询借款信息
        BorrowInfo borrowInfo = borrowInfoService.getById(borrowInfoApprovalVO.getId());
        //设置id为空，保存标的数据到数据库时会自动生成id
        lend.setId(null);
        lend.setUserId(borrowInfo.getUserId());//borrowInfo
        lend.setReturnMethod(borrowInfo.getReturnMethod());//borrowInfo
        lend.setBorrowInfoId(borrowInfoApprovalVO.getId());
        lend.setLendNo(LendNoUtils.getLendNo()); //生成唯一的id
        lend.setLendStartDate(new DateTime(borrowInfoApprovalVO.getLendStartDate())
                .toDate());
        lend.setLendEndDate(new DateTime(borrowInfoApprovalVO.getLendStartDate())
                .plusMonths(borrowInfo.getPeriod())
                .toDate());//起息日+标的的期数  就是最后结息日
        //将借款信息年化转为月化:  年化/12=月化    前端提交的年化*100
        BigDecimal monthRate = borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(1200), 6, BigDecimal.ROUND_DOWN);
        lend.setExpectAmount(borrowInfo.getAmount()
                .multiply(monthRate)
                .multiply(new BigDecimal(borrowInfo.getPeriod())));//期数*月化率*借款金额    预期收益
        lend.setRealAmount(new BigDecimal(0));
        //标的创建  后投资人无法查看，经过管理员审批通过  标的才会发布     标的募资成功后 由管理员来放款
        lend.setInvestNum(0);
        lend.setInvestAmount(new BigDecimal(0));

        lend.setAmount(borrowInfo.getAmount());
        lend.setPeriod(borrowInfo.getPeriod());
        //起投金额
        lend.setLowestAmount(new BigDecimal(50));
        this.save(lend);
    }

    @Override
    public List<Lend> getLends() {
        List<Lend> lends = this.list(Wrappers.lambdaQuery(Lend.class)
                .orderByDesc(Lend::getCreateTime));
        lends.forEach(lend -> {
            Map<String, String> params = new HashMap<>();
            String status = getStatus(lend);
            params.put("status", status);
            params.put("lendYearRate", lend.getLendYearRate() + "%");
            params.put("returnMethod", dictService
                    .getDictNameByDictCodeAndValue("returnMethod", lend.getReturnMethod()));
            lend.setParams(params);
        });
        return lends;
    }

    public String getStatus(Lend lend) {
        String status = null;
        switch (lend.getStatus()) {
            case 0:
                status = "待发布";
                break;
            case 1:
                status = "募资中";
                break;
            case 2:
                status = "还款中";
                break;
            case 3:
                status = "已结清";
                break;
            case 4:
                status = "结标";
                break;
            case -1:
                status = "已撤标";
                break;
        }
        return status;
    }

    //根据id查询标的详情
    @Override
    public LendInfoVO getLendInfoVOById(Long id) {
        LendInfoVO lendInfoVO = new LendInfoVO();
        //1、查询标的详情+数据组装
        Lend lend = this.getById(id);
        setParams2Lend(lend);
        lendInfoVO.setLend(lend);
        //2、查询借款人详情
        Long userId = lend.getUserId();
        BorrowerDetailVO borrowerDetailVO = borrowerService.queryBorrowerVOByUserId(userId);
        lendInfoVO.setBorrowerDetailVO(borrowerDetailVO);
        return lendInfoVO;
    }

    public void setParams2Lend(Lend lend) {
        Map<String, String> params = new HashMap<>();
        String status = getStatus(lend);
        params.put("status", status);
        params.put("serviceRate", lend.getServiceRate() + "%");
        params.put("lendYearRate", lend.getLendYearRate() + "%");
        params.put("returnMethod", dictService
                .getDictNameByDictCodeAndValue("returnMethod", lend.getReturnMethod()));
        lend.setParams(params);
    }

    @Override
    public Page<Lend> getLendsByCondition(LendSearchVO lendSearchVO, Integer pageNum, Integer pageSize) {
        LambdaQueryWrapper<Lend> queryWrapper = Wrappers.lambdaQuery(Lend.class);
        //1、解析条件，通过queryWrapper组装条件
        List<Integer> types = lendSearchVO.getType();
        List<Integer> lendYearRates = lendSearchVO.getLendYearRate();
        List<Integer> periods = lendSearchVO.getPeriod();
        List<Integer> returnMethods = lendSearchVO.getReturnMethod();
        if (!CollectionUtils.isEmpty(types)) {
            //"标的类型： 1-车易贷,2-房产贷,3-赎楼贷,4-债权贷，5-美容贷"
            //Lend中存的是标的的标题 没有类型，通过标题模糊查询
            //type集合中 类型有多个 可以使用like模糊查询 之间用or连接
            //   where (title like '房产贷' or  title like '美容贷')
            queryWrapper.and(t -> {
                types.forEach(type -> {
                    String typeDesc = "";
                    switch (type) {
                        case 1:
                            typeDesc = "车易贷";
                            break;
                        case 2:
                            typeDesc = "房产贷";
                            break;
                        case 3:
                            typeDesc = "赎楼贷";
                            break;
                        case 4:
                            typeDesc = "债权贷";
                            break;
                        case 5:
                            typeDesc = "美容贷";
                            break;
                    }
                    t.or().like(Lend::getTitle, typeDesc);
                });
            });

        }
        // 多个条件类型 之间使用and连接
        if (!CollectionUtils.isEmpty(lendYearRates)) {
            //"年利率： 1-12%以内，2-12%(包含)~14%,3-14%(包含)~16%,4-16%(包含)以上"
            //  区间查询使用and连接，  多个年利率之间使用or连接
            queryWrapper.and(t -> {
                // where xxx and  ( lendYearRate < 12 or (lendYearRate>=12 and lendYearRate<14 ) or  )
                lendYearRates.forEach(lendYearRate -> {
                    switch (lendYearRate) {
                        case 1://年化率<12
                            t.or().lt(Lend::getLendYearRate, 12);
                            break;
                        case 2:
                            t.or(a -> a.ge(Lend::getLendYearRate, 12).lt(Lend::getLendYearRate, 14));
                            break;
                        case 3:
                            t.or(a -> a.ge(Lend::getLendYearRate, 14).lt(Lend::getLendYearRate, 16));
                            break;
                        case 4:
                            t.or().ge(Lend::getLendYearRate, 16);
                            break;

                    }
                });
            });
        }
        if (!CollectionUtils.isEmpty(periods)) {
            //"期限：1-1月以下，2-1(包含)~3个月 ，3-3(包含)~6个月，4-6(包含)~12个月，5-12(包含)个月以上"
            //  区间查询使用and连接，  多个期限之间使用or连接
            queryWrapper.and(t -> {
                // where xxx and  ( lendYearRate < 12 or (lendYearRate>=12 and lendYearRate<14 ) or  )
                periods.forEach(period -> {
                    switch (period) {
                        case 1://1月以下
                            t.or().lt(Lend::getPeriod, 1);
                            break;
                        case 2:
                            t.or(a -> a.ge(Lend::getPeriod, 1).lt(Lend::getPeriod, 3));
                            break;
                        case 3:
                            t.or(a -> a.ge(Lend::getPeriod, 3).lt(Lend::getPeriod, 6));
                            break;
                        case 4:
                            t.or(a -> a.ge(Lend::getPeriod, 6).lt(Lend::getPeriod, 12));
                            break;
                        case 5:
                            t.or().ge(Lend::getPeriod, 12);
                            break;
                    }
                });
            });
        }
        if (!CollectionUtils.isEmpty(returnMethods)) {
            //"还款方式：1-等额本息，2-等额本金，4-到期还本付息，3-按月付息，到期还本"
            //lend表中存了还款方式的value值，只要前端提交的还款方式value值和他对应 直接可以使用in来查询
            queryWrapper.in(Lend::getReturnMethod, returnMethods);
        }
        //不能查询待发布和已撤标的标的
        queryWrapper.notIn(Lend::getStatus, 0, -1);
        //需要排序的表一般会设计sort字段
        queryWrapper.orderByDesc(Lend::getPublishDate);
        //2、业务类使用条件分页查询
        Page<Lend> page = this.page(new Page<Lend>(pageNum, pageSize), queryWrapper);
        //3、对查询到的数据集合 属性进行组装 交给页面展示
        page.getRecords().forEach(lend -> {
            setParams2Lend(lend);
        });
        return page;
    }

    @Override
    public BigDecimal income(BigDecimal amount, BigDecimal lendYearRate, Integer period, Integer returnMethod) {
        //因为数据库存储的年化率是 *100的值，工具类计算收益时 当做%100直接使用
        //保留小数点后6位  使用四舍五入的方式处理
        lendYearRate = lendYearRate.divide(new BigDecimal(100), 6, BigDecimal.ROUND_DOWN);
        //不同还款方式利息不一样：
        BigDecimal count = new BigDecimal("0");
        switch (returnMethod) {
            case 1:
                count = Amount1Helper.getInterestCount(amount, lendYearRate, period);
                break;
            case 2:
                count = Amount2Helper.getInterestCount(amount, lendYearRate, period);
                break;
            case 3:
                count = Amount3Helper.getInterestCount(amount, lendYearRate, period);
                break;
            case 4:
                count = Amount4Helper.getInterestCount(amount, lendYearRate, period);
                break;
        }
        System.out.println("count = " + count);
        return count;
    }

    @Override
    public Boolean updateInvestAmountAndNum(String agentProjectCode, String voteAmt) {
        return baseMapper.updateInvestAmountAndNum(agentProjectCode, voteAmt);
    }

    @Override
    public void makeLoan(Long id) {
        // 涉及的表：
        // hfb放款接口文档：
        //1、检查标的的状态： 是否是募资中、标的已投金额是否满足要求
        Lend lend = this.getById(id);
        SrbAssert.assertTrue(lend.getStatus() == 1, ResponseEnum.LEND_MAKELOAN_ERROR);
        //标的金额校验
        List<LendItem> lendItems = lendItemService.list(Wrappers.lambdaQuery(LendItem.class)
                .eq(LendItem::getLendId, lend.getId())
                .eq(LendItem::getStatus, 1));//查询本标的的投资成功的投资记录
        BigDecimal total = lendItems.stream()
                // 获取每个LendItem对象的InvestAmount属性值返回
                .map(LendItem::getInvestAmount)
                //对map返回的集合进行计算：
                // (a, b) -> a.add(b) : 对map返回的集合每两个进行处理  .add使用返回的一个对象调用它的方法
                .reduce((a, b) -> a.add(b))
                // 获取reduce执行后的结果
                .get();
        SrbAssert.assertTrue(lend.getInvestAmount().compareTo(total) == 0, ResponseEnum.LEND_MAKELOAN_AMOUNT_ERROR);
        //2、检查标的借款人的状态：
        UserInfo userInfo = userInfoService.getById(lend.getUserId());
        SrbAssert.assertNotTrue(userInfo.getStatus() == 0, ResponseEnum.LOGIN_LOCKED_ERROR);
        //  2.2 用户是否绑定hfb
        SrbAssert.assertNotTrue(userInfo.getBindStatus() != 1, ResponseEnum.USER_NO_BIND_ERROR);
        // 2.3 借款人审核状态
        SrbAssert.assertNotTrue(userInfo.getBorrowAuthStatus() != 2, ResponseEnum.USER_NO_AMOUNT_ERROR);
        //3、访问hfb 的放款接口：同步访问hfb放款接口(hfb)
        // hfb将该标的投资人投资的金额转给借款人
        // 放款后的业务需要同步处理，所以此处不采用之前的回调方式来处理
        Map<String, Object> params = new HashMap<>();
        params.put("agentId", HfbConst.AGENT_ID);//商户编号
        params.put("agentProjectCode", lend.getLendNo());//和投资人投资时 传给hfb的项目编号一定要一样
        String loanNo = LendNoUtils.getLoanNo();
        params.put("agentBillNo", loanNo);//本地放款的单号
        //给用户放款时可以收取手续费： hfb操作时 转给借款人账户的钱会扣除此部分 转到商户账户中
        //计算手续费： 标的已投金额*标的的服务费月化率*期数
        BigDecimal monthServiceRate = lend.getServiceRate().divide(new BigDecimal(1200), 6, BigDecimal.ROUND_DOWN);

        BigDecimal serviceAmount = lend.getInvestAmount().multiply(new BigDecimal(lend.getPeriod())).multiply(monthServiceRate);
        params.put("mchFee", serviceAmount);
        params.put("note", "单纯的测试");
        params.put("timestamp", System.currentTimeMillis());
        params.put("sign", RequestHelper.getSign(params));
        //发起请求 得到响应结果
        JSONObject jsonObject = RequestHelper.sendRequest(params, HfbConst.MAKE_LOAN_URL);
        //解析响应结果
        Object resultCode = jsonObject.get("resultCode");
        //=============保存放款的日志
        TransFlow transFlow = new TransFlow();
        transFlow.setUserId(userInfo.getId());//借款人id
        transFlow.setUserName(userInfo.getName());//借款人姓名
        transFlow.setTransNo(loanNo);//放款编号
        transFlow.setTransType(5);//5：放款
        transFlow.setTransTypeName("放款");
        transFlow.setTransAmount(lend.getInvestAmount());
        transFlow.setStatus("0000".equals(resultCode) ? 1 : 0);
        transFlow.setMemo(jsonObject.toJSONString());
        transFlowService.save(transFlow);

        SrbAssert.assertTrue("0000".equals(resultCode), ResponseEnum.LEND_MAKELOAN_HFB_ERROR);

        BigDecimal mchFee = jsonObject.getBigDecimal("mchFee");
        //5、更新放款成功的标的的状态
        updateLendStatus(lend, mchFee);
        //4、如果hfb放款成功：
        //4.1 将hfb放款金额(转给借款人hfb账户)添加到借款人账户中
        BigDecimal voteAmt = new BigDecimal(jsonObject.get("voteAmt").toString());
        userAccountService.updateAmountByBindCode(voteAmt, userInfo.getBindCode());
        //放款 转账到借款人账户的日志支持 借款人查询  但是放款日志只能管理员查询
        transFlow = new TransFlow();
        transFlow.setUserId(userInfo.getId());//借款人id
        transFlow.setUserName(userInfo.getName());//借款人姓名
        transFlow.setTransNo(LendNoUtils.getNo());//数据库表唯一键约束 此值已经在上面保存了一个日志
        transFlow.setTransType(6);//5：放款
        transFlow.setTransTypeName("借款");
        transFlow.setTransAmount(voteAmt);
        transFlow.setStatus(1);
        transFlowService.save(transFlow);

        //4.2 将标的投资人 该标的冻结金额扣除
        //使用上面查询到的投资成功的投资记录列表
        lendItems.forEach(lendItem -> {
            Long investUserId = lendItem.getInvestUserId();
            BigDecimal investAmount = lendItem.getInvestAmount();
            userAccountService.updateFreezeAmountByUserId(investUserId, investAmount);
            TransFlow transFlow2 = new TransFlow();
            transFlow2.setUserId(investUserId);//投资人id
            transFlow2.setUserName(lendItem.getInvestName());//投资人姓名
            transFlow2.setTransNo(LendNoUtils.getNo());//
            transFlow2.setTransType(7);//7:投资成功
            transFlow2.setTransTypeName("投资成功");
            transFlow2.setTransAmount(lendItem.getInvestAmount());
            transFlow2.setStatus(1);
            transFlowService.save(transFlow2);
        });

        //6、生成还款计划(借款人：)
        //一个借款记录的还款计划 可以使用对应的工具类一次获取到它所有期数的 还款本金或利息
        BigDecimal investAmount = lend.getInvestAmount();
        BigDecimal yearRate = lend.getLendYearRate().divide(new BigDecimal(100));
        Integer period = lend.getPeriod();
        Integer returnMethod = lend.getReturnMethod();
        Map<String, Map<Integer, BigDecimal>> map = new HashMap<>();
        //通过容器装配的对象 会被配置的aop代理
        List<LendReturn> lendReturns = lendReturnService.saveLendReturns(lend, userInfo);

        //下面的回款计划需要根据期数快速获取对应的还款计划的id
        Map<Integer, Long> lendReturnPeriodId = lendReturns.stream()
                //将lendReturns集合中的每个对象的 getCurrentPeriod属性 和 id属性 以k-v的形式转为map
                .collect(Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId));
        // 7、生成回款计划(投资人：)
        //查询成功的投资记录
        //每个投资生成 标的期数个回款计划
        List<LendItemReturn> lendItemReturns = lendItemReturnService
                .saveLendItemReturns(lend, lendItems, yearRate, period, returnMethod, lendReturnPeriodId);
        //8、批量更新还款计划：给每个还款设置本金利息 本息
        lendReturnService.updateLendReturns(lendReturns, lendItemReturns);
    }

    public void updateLendStatus(Lend lend, BigDecimal mchFee) {
        lend.setStatus(2);//还款中
        //realAmount: 平台的服务费
        lend.setRealAmount(mchFee);
        lend.setPaymentAdminId(1L);//本次放款的管理员id
        lend.setPaymentTime(new Date());
        this.updateById(lend);
    }

}
